home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-desktop-9.10-i386-PL.iso / casper / filesystem.squashfs / usr / share / ibus-table / engine / table.py < prev    next >
Text File  |  2009-07-30  |  60KB  |  1,523 lines

  1. # -*- coding: utf-8 -*-
  2. # vim: set et ts=4 sts=4
  3. #
  4. # ibus-table - The Tables engine for IBus
  5. #
  6. # Copyright (c) 2008-2009 Yu Yuwei <acevery@gmail.com>
  7. #
  8. # This library is free software; you can redistribute it and/or
  9. # modify it under the terms of the GNU Lesser General Public
  10. # License as published by the Free Software Foundation; either
  11. # version 2.1 of the License, or (at your option) any later version.
  12. #
  13. # This library is distributed in the hope that it will be useful,
  14. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  16. # Lesser General Public License for more details.
  17. #
  18. # You should have received a copy of the GNU Lesser General Public
  19. # License along with this library; if not, write to the Free Software
  20. # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
  21. #
  22. # $Id: $
  23. #
  24. __all__ = (
  25.     "tabengine",
  26. )
  27.  
  28. import os
  29. import ibus
  30. #from ibus import Property
  31. from ibus import keysyms
  32. from ibus import modifier
  33. from ibus import ascii
  34. #import tabsqlitedb
  35. import tabdict
  36. import re
  37. patt_edit = re.compile (r'(.*)###(.*)###(.*)')
  38. patt_uncommit = re.compile (r'(.*)@@@(.*)')
  39.  
  40. from gettext import dgettext
  41. _  = lambda a : dgettext ("ibus-table", a)
  42. N_ = lambda a : a
  43.  
  44. import dbus
  45.  
  46. class KeyEvent:
  47.     def __init__(self, keyval, is_press, state):
  48.         self.code = keyval
  49.         self.mask = state
  50.         if not is_press:
  51.             self.mask |= modifier.RELEASE_MASK
  52.     def __str__(self):
  53.         return "%s 0x%08x" % (keysyms.keycode_to_name(self.code), self.mask)
  54.  
  55.  
  56. class editor(object):
  57.     '''Hold user inputs chars and preedit string'''
  58.     def __init__ (self,phrase_table_index,valid_input_chars, max_key_length, database, parser = tabdict.parse, deparser = tabdict.deparse, max_length = 64):
  59.         self.db = database
  60.         self._pt = phrase_table_index
  61.         self._parser = parser
  62.         self._deparser = deparser
  63.         self._max_key_len = int(max_key_length)
  64.         self._max_length = max_length
  65.         self._valid_input_chars = valid_input_chars
  66.         #
  67.         # below vals will be reset in self.clear()
  68.         #
  69.         # we hold this: [str,str,...]
  70.         # self._chars: hold user input in table mode (valid,invalid,prevalid)
  71.         self._chars = [[],[],[]]
  72.         #self._t_chars: hold total input for table mode for input check
  73.         self._t_chars = []
  74.         # self._u_chars: hold user input but not manual comitted chars
  75.         self._u_chars = []
  76.         # self._tabkey_list: hold tab_key objects transform from user input chars
  77.         self._tabkey_list = []
  78.         # self._strings: hold preedit strings
  79.         self._strings = []
  80.         # self._cursor: the caret position in preedit phrases 
  81.         self._cursor = [0,0]
  82.         # self._candidates: hold candidates selected from database [[now],[pre]]
  83.         self._candidates = [[],[]]
  84.         self._lookup_table = ibus.LookupTable (tabengine._page_size)
  85.         # self._py_mode: whether in pinyin mode
  86.         self._py_mode = False
  87.         # self._zi: the last Zi commit to preedit
  88.         self._zi = u''
  89.         # self._caret: caret position in lookup_table
  90.         self._caret = 0
  91.         # self._onechar: whether we only select single character
  92.         self._onechar = False
  93.         # self._chinese_mode: the candidate filter mode,
  94.         #   0 is simplify Chinese
  95.         #   1 is traditional Chinese
  96.         #   2 is Big charset mode, but simplify Chinese first
  97.         #   3 is Big charset mode, but traditional Chinese first
  98.         #   4 is Big charset mode.
  99.         # we use LC_CTYPE to determine which one to use
  100.         self._chinese_mode = self.get_chinese_mode()
  101.  
  102.     def get_chinese_mode (self):
  103.         '''Use LC_CTYPE in your box to determine the _chinese_mode'''
  104.         try:
  105.             if os.environ.has_key('LC_CTYPE'):
  106.                 __lc = os.environ['LC_CTYPE'].split('.')[0].lower()
  107.             else:
  108.                 __lc = os.environ['LANG'].split('.')[0].lower()
  109.  
  110.             if __lc.find('zh_') == 0:
  111.                 # this is a zh_XX
  112.                 __place =__lc.split('_')[1]
  113.                 if __place == 'cn':
  114.                     return 0
  115.                 else:
  116.                     return 1
  117.             else:
  118.                 if self.db._is_chinese():
  119.                     # if IME declare as Chinese IME
  120.                     return 0
  121.                 else:
  122.                     return -1
  123.         except:
  124.             return -1
  125.                 
  126.     def change_chinese_mode (self):
  127.         if self._chinese_mode != -1:
  128.             self._chinese_mode = (self._chinese_mode +1 ) % 5
  129.  
  130.     def clear (self):
  131.         '''Remove data holded'''
  132.         self.over_input ()
  133.         self._t_chars = []
  134.         self._strings = []
  135.         self._cursor = [0,0]
  136.         self._py_mode = False
  137.         self._zi = u''
  138.         self.update_candidates
  139.     
  140.     def is_empty (self):
  141.         return len(self._t_chars) == 0
  142.  
  143.     def clear_input (self):
  144.         '''
  145.         Remove input characters held for Table mode,
  146.         '''
  147.         self._chars = [[],[],[]]
  148.         self._tabkey_list = []
  149.         self._lookup_table.clean()
  150.         self._lookup_table.show_cursor(False)
  151.         self._candidates = [[],[]]
  152.     
  153.     def over_input (self):
  154.         '''
  155.         Remove input characters held for Table mode,
  156.         '''
  157.         self.clear_input ()
  158.         self._u_chars = []
  159.     
  160.     def set_parser (self, parser):
  161.         '''change input parser'''
  162.         self.clear ()
  163.         self._parser = parser
  164.         
  165.     def add_input (self,c):
  166.         '''add input character'''
  167.         if len (self._t_chars) == self._max_length:
  168.             return True
  169.         self._zi = u''
  170.         if self._cursor[1]:
  171.             self.split_phrase()
  172.         if (len (self._chars[0]) == self._max_key_len and (not self._py_mode)) or ( len (self._chars[0]) == 7 and self._py_mode ) :
  173.             self.auto_commit_to_preedit()
  174.             res = self.add_input (c)
  175.             return res
  176.         elif self._chars[1]:
  177.             self._chars[1].append (c)
  178.         else:
  179.             if (not self._py_mode and ( c in self._valid_input_chars)) or\
  180.                 (self._py_mode and (c in u'abcdefghijklmnopqrstuvwxyz!@#$%')):
  181.                 try:
  182.                     self._tabkey_list += self._parser (c)
  183.                     self._chars[0].append (c)
  184.                 except:
  185.                     self._chars[1].append (c)
  186.             else:
  187.                 self._chars[1].append (c)
  188.         self._t_chars.append(c)
  189.         res = self.update_candidates ()
  190.         return res
  191.  
  192.     def pop_input (self):
  193.         '''remove and display last input char held'''
  194.         _c =''
  195.         if self._chars[1]:
  196.             _c = self._chars[1].pop ()
  197.         elif self._chars[0]:
  198.             _c = self._chars[0].pop ()
  199.             self._tabkey_list.pop()
  200.             if (not self._chars[0]) and self._u_chars:
  201.                 self._chars[0] = self._u_chars.pop()
  202.                 self._chars[1] = self._chars[1][:-1]
  203.                 self._tabkey_list = self._parser (self._chars[0])
  204.                 self._strings.pop (self._cursor[0] - 1 )
  205.                 self._cursor[0] -= 1
  206.         self._t_chars.pop()
  207.         self.update_candidates ()
  208.         return _c
  209.     
  210.     def get_input_chars (self):
  211.         '''get characters held, valid and invalid'''
  212.         return self._chars[0] + self._chars[1]
  213.  
  214.     def get_input_chars_string (self):
  215.         '''Get valid input char string'''
  216.         return u''.join(map(str,self._t_chars))
  217.  
  218.     def get_all_input_strings (self):
  219.         '''Get all uncommit input characters, used in English mode or direct commit'''
  220.         return  u''.join( map(u''.join, self._u_chars + [self._chars[0]] \
  221.             + [self._chars[1]]) )
  222.     
  223.     def get_index(self,key):
  224.         '''Get the index of key in database table'''
  225.         return self._pt.index(key)
  226.  
  227.     def split_phrase (self):
  228.         '''Splite current phrase into two phrase'''
  229.         _head = u''
  230.         _end = u''
  231.         try:
  232.             _head = self._strings[self._cursor[0]][:self._cursor[1]]
  233.             _end = self._strings[self._cursor[0]][self._cursor[1]:]
  234.             self._strings.pop(self._cursor[0])
  235.             self._strings.insert(self._cursor[0],_head)
  236.             self._strings.insert(self._cursor[0]+1,_end)
  237.             self._cursor[0] +=1
  238.             self._cursor[1] = 0
  239.         except:
  240.             pass
  241.     
  242.     def remove_before_string (self):
  243.         '''Remove string before cursor'''
  244.         if self._cursor[1] != 0:
  245.             self.split_phrase()
  246.         if self._cursor[0] > 0:
  247.             self._strings.pop(self._cursor[0]-1)
  248.             self._cursor[0] -= 1
  249.         else:
  250.             pass
  251.         # if we remove all characters in preedit string, we need to clear the self._t_chars
  252.         if self._cursor == [0,0]:
  253.             self._t_chars =[]
  254.     
  255.     def remove_after_string (self):
  256.         '''Remove string after cursor'''
  257.         if self._cursor[1] != 0:
  258.             self.split_phrase()
  259.         if self._cursor[0] >= len (self._strings):
  260.             pass
  261.         else:
  262.             self._strings.pop(self._cursor[0])
  263.     
  264.     def remove_before_char (self):
  265.         '''Remove character before cursor'''
  266.         if self._cursor[1] > 0:
  267.             _str = self._strings[ self._cursor[0] ]
  268.             self._strings[ self._cursor[0] ] = _str[ : self._cursor[1]-1] + _str[ self._cursor[1] :]
  269.             self._cursor[1] -= 1
  270.         else:
  271.             if self._cursor[0] == 0:
  272.                 pass
  273.             else:
  274.                 if len ( self._strings[self._cursor[0] - 1] ) == 1:
  275.                     self.remove_before_string()
  276.                 else:
  277.                     self._strings[self._cursor[0] - 1] = self._strings[self._cursor[0] - 1][:-1]
  278.         # if we remove all characters in preedit string, we need to clear the self._t_chars
  279.         if self._cursor == [0,0]:
  280.             self._t_chars =[]
  281.  
  282.     def remove_after_char (self):
  283.         '''Remove character after cursor'''
  284.         if self._cursor[1] == 0:
  285.             if self._cursor[0] == len ( self._strings):
  286.                 pass
  287.             else:
  288.                 if len( self._strings[ self._cursor[0] ]) == 1:
  289.                     self.remove_after_string ()
  290.                 else:
  291.                     self._strings[ self._cursor[0] ] = self._strings[ self._cursor[0] ][1:]
  292.         else:
  293.             if ( self._cursor[1] + 1 ) == len( self._strings[ self._cursor[0] ] ) :
  294.                 self.split_phrase ()
  295.                 self.remove_after_string ()
  296.             else:
  297.                 string = self._strings[ self._cursor[0] ]
  298.                 self._strings[ self._cursor[0] ] = string[:self._cursor[1]] + string[ self._cursor[1] + 1 : ]
  299.  
  300.     def get_invalid_input_chars (self):
  301.         '''get invalid characters held'''
  302.         return self._chars[1]
  303.  
  304.     def get_invalid_input_string (self):
  305.         '''get invalid characters in string form'''
  306.         return u''.join (self._chars[1])
  307.         
  308.     def get_preedit_strings (self):
  309.         '''Get preedit strings'''
  310.         if self._candidates[0]:
  311.             if self._py_mode:
  312.                 _p_index = 8
  313.             else:
  314.                 _p_index = self.get_index ('phrase')
  315.             _candi = u'###' + self._candidates[0][ int (self._lookup_table.get_cursor_pos() ) ][ _p_index ] + u'###' 
  316.         else:
  317.             input_chars = self.get_input_chars ()
  318.             if input_chars:
  319.                 _candi = u''.join( ['###'] + map( str, input_chars) + ['###'] )
  320.             else:
  321.                 _candi = u''
  322.         if self._strings:
  323.             res = u''
  324.             _cursor = self._cursor[0]
  325.             _luc = len (self._u_chars)
  326.             if _luc:
  327.                 _candi = _candi == u'' and u'######' or _candi
  328.                 res =u''.join( self._strings[ : _cursor - _luc] +[u'@@@'] + self._strings[_cursor - _luc : _cursor ]  + [ _candi  ] + self._strings[ _cursor : ])
  329.             else:
  330.                 res = u''.join( self._strings[ : _cursor ] + [ _candi  ] + self._strings[ _cursor : ])
  331.             return res
  332.         else:
  333.             return _candi 
  334.     def add_caret (self, addstr):
  335.         '''add length to caret position'''
  336.         self._caret += len(addstr)
  337.  
  338.     def get_caret (self):
  339.         '''Get caret position in preedit strings'''
  340.         self._caret = 0
  341.         if self._cursor[0] and self._strings:
  342.             map (self.add_caret,self._strings[:self._cursor[0]])
  343.         self._caret += self._cursor[1]
  344.         if self._candidates[0]:
  345.             if self._py_mode:
  346.                 _p_index = 8
  347.             else:
  348.                 _p_index = self.get_index ('phrase')
  349.             _candi =self._candidates[0][ int (self._lookup_table.get_cursor_pos() ) ][ _p_index ] 
  350.         else:
  351.             _candi = u''.join( map( str,self.get_input_chars()) )
  352.         self._caret += len( _candi ) 
  353.         return self._caret
  354.     
  355.     def arrow_left (self):
  356.         '''Process Arrow Left Key Event.
  357.         Update cursor data when move caret left'''
  358.         if self.get_preedit_strings ():
  359.             if not( self.get_input_chars () or self._u_chars ):
  360.                 if self._cursor[1] > 0:
  361.                     self._cursor[1] -= 1
  362.                 else:
  363.                     if self._cursor[0] > 0:
  364.                         self._cursor[1] = len (self._strings[self._cursor[0]-1]) - 1
  365.                         self._cursor[0] -= 1
  366.                     else:
  367.                         self._cursor[0] = len(self._strings)
  368.                         self._cursor[1] = 0
  369.                 self.update_candidates ()
  370.             return True
  371.         else:
  372.             return False
  373.     
  374.     def arrow_right (self):
  375.         '''Process Arrow Right Key Event.
  376.         Update cursor data when move caret right'''
  377.         if self.get_preedit_strings ():
  378.             if not( self.get_input_chars () or self._u_chars ):
  379.                 if self._cursor[1] == 0:
  380.                     if self._cursor[0] == len (self._strings):
  381.                         self._cursor[0] = 0
  382.                     else:
  383.                         self._cursor[1] += 1
  384.                 else:
  385.                     self._cursor[1] += 1
  386.                 if self._cursor[1] == len(self._strings[ self._cursor[0] ]):
  387.                     self._cursor[0] += 1
  388.                     self._cursor[1] = 0
  389.                 self.update_candidates ()
  390.             return True
  391.         else:
  392.             return False
  393.  
  394.     def control_arrow_left (self):
  395.         '''Process Control + Arrow Left Key Event.
  396.         Update cursor data when move caret to string left'''
  397.         if self.get_preedit_strings ():
  398.             if not( self.get_input_chars () or self._u_chars ):
  399.                 if self._cursor[1] == 0:
  400.                     if self._cursor[0] == 0:
  401.                         self._cursor[0] = len (self._strings) - 1
  402.                     else:
  403.                         self._cursor[0] -= 1
  404.                 else:
  405.                     self._cursor[1] = 0
  406.                 self.update_candidates ()
  407.             return True
  408.         else:
  409.             return False
  410.     
  411.     def control_arrow_right (self):
  412.         '''Process Control + Arrow Right Key Event.
  413.         Update cursor data when move caret to string right'''
  414.         if self.get_preedit_strings ():
  415.             if not( self.get_input_chars () or self._u_chars ):
  416.                 if self._cursor[1] == 0:
  417.                     if self._cursor[0] == len (self._strings):
  418.                         self._cursor[0] = 1
  419.                     else:
  420.                         self._cursor[0] += 1
  421.                 else:
  422.                     self._cursor[0] += 1
  423.                     self._cursor[1] = 0
  424.                 self.update_candidates ()
  425.             return True
  426.         else:
  427.             return False
  428.     def ap_candidate (self, candi):
  429.         '''append candidate to lookup_table'''
  430.         if not self._py_mode:
  431.             _p_index = self.get_index('phrase')
  432.             _fkey = self.get_index('m0')
  433.         else:
  434.             _p_index = 8
  435.             _fkey = 1
  436.         if self.db._is_chinese:
  437.             _tbks = u''.join( map(self._deparser , candi[_fkey + len(self._tabkey_list) : _p_index-1 ] ) )
  438.             if self._py_mode:
  439.                 # restore tune symbol
  440.                 _tbks = _tbks.replace('!','Γåæ1').replace('@','Γåæ2').replace('#','Γåæ3').replace('$','Γåæ4').replace('%','Γåæ5')
  441.         else:
  442.             _tbks = u''.join( map(self._deparser , candi[_fkey + len(self._tabkey_list) : _p_index ] ) )
  443.         _phrase = candi[_p_index]
  444.         # further color implementation needed :)
  445.         # here -2 is the pos of num, -1 is the pos of . 0 is the pos of string
  446.         #attrs = ibus.AttrList ([ibus.AttributeForeground (0x8e2626, -2, 1)])
  447.         attrs = ibus.AttrList ()
  448.         # this is the part of tabkey
  449.         attrs.append( ibus.AttributeForeground ( 0x1973a2, 0, \
  450.             len(_phrase) + len(_tbks)))
  451.         if candi[-2] < 0:
  452.             # this is a user defined phrase:
  453.             attrs.append ( ibus.AttributeForeground (0x7700c3, 0, len(_phrase)) )
  454.         elif candi[-1] > 0:
  455.             # this is a sys phrase used by user:
  456.             attrs.append ( ibus.AttributeForeground (0x000000, 0, len(_phrase)) )
  457.         else:
  458.             # this is a system phrase haven't been used:
  459.             attrs.append ( ibus.AttributeForeground (0x000000, 0, len(_phrase)) )
  460.         self._lookup_table.append_candidate ( ibus.Text(_phrase + _tbks, attrs) )
  461.         self._lookup_table.show_cursor (False)
  462.  
  463.     def filter_candidates (self, candidates):
  464.         '''Filter candidates if IME is Chinese'''
  465.         #if self.db._is_chinese and (not self._py_mode):
  466.         if not self._chinese_mode in(2,3):
  467.             return candidates[:]
  468.         bm_index = self._pt.index('category')
  469.         if self._chinese_mode == 2:
  470.             # big charset with SC first
  471.             return  filter (lambda x: x[bm_index] & 1, candidates)\
  472.                     +filter (lambda x: x[bm_index] & (1 << 1) and \
  473.                             (not x[bm_index] & 1), candidates)\
  474.                     + filter (lambda x: x[bm_index] & (1 << 2), candidates)
  475.         elif self._chinese_mode == 3:
  476.             # big charset with SC first
  477.             return  filter (lambda x: x[bm_index] & (1 << 1), candidates)\
  478.                     +filter (lambda x: x[bm_index] & 1 and\
  479.                     (not x[bm_index] & (1<<1)) , candidates)\
  480.                     + filter (lambda x: x[bm_index] & (1 << 2), candidates)
  481.  
  482.     def update_candidates (self):
  483.         '''Update lookuptable'''
  484.         if (self._chars[0] == self._chars[2] and self._candidates[0]) \
  485.                 or self._chars[1]:
  486.             # if no change in valid input char or we have invalid input,
  487.             # we do not do sql enquery
  488.             pass
  489.         else:
  490.             # check whether last time we have only one candidate
  491.             only_one_last = self.one_candidate()
  492.             # do enquiry
  493.             self._lookup_table.clean ()
  494.             self._lookup_table.show_cursor (False)
  495.             if self._tabkey_list:
  496.                 # here we need to consider two parts, table and pinyin
  497.                 # first table
  498.                 if not self._py_mode:
  499.                     if self.db._is_chinese :
  500.                         bm_index = self._pt.index('category')
  501.                         if self._chinese_mode == 0:
  502.                             # simplify Chinese mode
  503.                             self._candidates[0] = self.db.select_words(\
  504.                                     self._tabkey_list, self._onechar, 1 )
  505.                         elif self._chinese_mode == 1:
  506.                             # traditional Chinese mode
  507.                             self._candidates[0] = self.db.select_words(\
  508.                                     self._tabkey_list, self._onechar, 2 )
  509.                         else:
  510.                             self._candidates[0] = self.db.select_words(\
  511.                                     self._tabkey_list, self._onechar )
  512.                     else:
  513.                         self._candidates[0] = self.db.select_words( self._tabkey_list, self._onechar )
  514.                 else:
  515.                     self._candidates[0] = self.db.select_zi( self._tabkey_list )
  516.                 self._chars[2] = self._chars[0][:]
  517.                     
  518.             else:
  519.                 self._candidates[0] =[]
  520.             if self._candidates[0]:
  521.                 self._candidates[0] = self.filter_candidates (self._candidates[0])
  522.             if self._candidates[0]:
  523.                 map ( self.ap_candidate,self._candidates[0] )
  524.             else:
  525.                 if self._chars[0]:
  526.                     ## old manner:
  527.                     #if self._candidates[1]:
  528.                     #    #print self._candidates[1]
  529.                     #    self._candidates[0] = self._candidates[1]
  530.                     #    self._candidates[1] = []
  531.                     #    last_input = self.pop_input ()
  532.                     #    self.auto_commit_to_preedit ()
  533.                     #    res = self.add_input( last_input )
  534.                     #    print res
  535.                     #    return res
  536.                     #else:
  537.                     #    self.pop_input ()
  538.                     #    self._lookup_table.clean()
  539.                     #    self._lookup_table.show_cursor (False)
  540.                     #    return False
  541.                     ###################
  542.                     ## new manner, we add new char to invalid input
  543.                     ## chars
  544.                     if not self._chars[1]:
  545.                         # we don't have invalid input chars
  546.                         # here we need to check the last input
  547.                         # is a punctuation or not, if is a punct,
  548.                         # then we use old maner to summit the former valid
  549.                         # candidate
  550.                         if ascii.ispunct (self._chars[0][-1].encode('ascii')) \
  551.                                 or len (self._chars[0][:-1]) \
  552.                                 in self.db.pkeylens \
  553.                                 or only_one_last:
  554.                             # because we use [!@#$%] to denote [12345]
  555.                             # in py_mode, so we need to distinguish them
  556.                             ## old manner:
  557.                             if self._py_mode:
  558.                                 if self._chars[0][-1] in "!@#$%":
  559.                                     self._chars[0].pop() 
  560.                                     self._tabkey_list.pop()
  561.                                     return True
  562.  
  563.                             if self._candidates[1]:
  564.                                 self._candidates[0] = self._candidates[1]
  565.                                 self._candidates[1] = []
  566.                                 last_input = self.pop_input ()
  567.                                 self.auto_commit_to_preedit ()
  568.                                 res = self.add_input( last_input )
  569.                                 return res
  570.                             else:
  571.                                 self.pop_input ()
  572.                                 self._lookup_table.clean()
  573.                                 self._lookup_table.show_cursor (False)
  574.                                 return False
  575.                         else:    
  576.                             # this is not a punct or not a valid phrase
  577.                             # last time
  578.                             self._chars[1].append( self._chars[0].pop() )
  579.                             self._tabkey_list.pop()
  580.                     else:
  581.                         pass
  582.                     self._candidates[0] =[]
  583.                 else:
  584.                     self._lookup_table.clean()
  585.                     self._lookup_table.show_cursor (False)
  586.             self._candidates[1] = self._candidates[0]
  587.         return True    
  588.  
  589.     def commit_to_preedit (self):
  590.         '''Add select phrase in lookup table to preedit string'''
  591.         if not self._py_mode:
  592.             _p_index = self.get_index('phrase')
  593.         else:
  594.             _p_index = 8
  595.         try:
  596.             self._strings.insert(self._cursor[0], self._candidates[0][ self.get_cursor_pos() ][_p_index])
  597.             self._cursor [0] += 1
  598.             if self._py_mode:
  599.                 self._zi = self._candidates[0][ self.get_cursor_pos() ][_p_index]
  600.             self.over_input ()
  601.             self.update_candidates ()
  602.         except:
  603.             pass
  604.     
  605.     def auto_commit_to_preedit (self):
  606.         '''Add select phrase in lookup table to preedit string'''
  607.         if not self._py_mode:
  608.             _p_index = self.get_index('phrase')
  609.         else:
  610.             _p_index = 8
  611.         try:
  612.             self._u_chars.append( self._chars[0][:] )
  613.             self._strings.insert(self._cursor[0], self._candidates[0][ self.get_cursor_pos() ][_p_index])
  614.             self._cursor [0] += 1
  615.             self.clear_input()
  616.             self.update_candidates ()
  617.         except:
  618.             pass
  619.  
  620.     def get_aux_strings (self):
  621.         '''Get aux strings'''
  622.         input_chars = self.get_input_chars ()
  623.         if input_chars:
  624.             #aux_string =  u' '.join( map( u''.join, self._u_chars + [self._chars[0]] ) )
  625.             aux_string =   u''.join (self._chars[0]) 
  626.             if self._py_mode:
  627.                 aux_string = aux_string.replace('!','1').replace('@','2').replace('#','3').replace('$','4').replace('%','5')
  628.             return aux_string
  629.  
  630.         aux_string = u''
  631.         if self._zi:
  632.             # we have pinyin result
  633.             tabcodes = self.db.find_zi_code(self._zi)
  634.             aux_string = self._zi+u': '
  635.             aux_string = u' '.join(tabcodes)
  636.         #    self._zi = u''
  637.         cstr = u''.join(self._strings)
  638.         if self.db.user_can_define_phrase:
  639.             if len (cstr ) > 1:
  640.                 aux_string += (u'\t#: ' + self.db.parse_phrase_to_tabkeys (cstr))
  641.         return aux_string
  642.     def arrow_down(self):
  643.         '''Process Arrow Down Key Event
  644.         Move Lookup Table cursor down'''
  645.         res = self._lookup_table.cursor_down()
  646.         self.update_candidates ()
  647.         if not res and self._candidates[0]:
  648.             return True
  649.         return res
  650.     
  651.     def arrow_up(self):
  652.         '''Process Arrow Up Key Event
  653.         Move Lookup Table cursor up'''
  654.         res = self._lookup_table.cursor_up()
  655.         self.update_candidates ()
  656.         if not res and self._candidates[0]:
  657.             return True
  658.         return res
  659.     
  660.     def page_down(self):
  661.         '''Process Page Down Key Event
  662.         Move Lookup Table page down'''
  663.         res = self._lookup_table.page_down()
  664.         self.update_candidates ()
  665.         if not res and self._candidates[0]:
  666.             return True
  667.         return res
  668.     
  669.     def page_up(self):
  670.         '''Process Page Up Key Event
  671.         move Lookup Table page up'''
  672.         res = self._lookup_table.page_up()
  673.         self.update_candidates ()
  674.         if not res and self._candidates[0]:
  675.             return True
  676.         return res
  677.     
  678.     def number (self, index):
  679.         '''Select the candidates in Lookup Table
  680.         index should start from 0'''
  681.         self._lookup_table.set_cursor_pos_in_current_page ( index )
  682.         if index != self._lookup_table.get_cursor_pos_in_current_page ():
  683.             # the index given is out of range we do not commit string
  684.             return False
  685.         self.commit_to_preedit ()
  686.         return True
  687.  
  688.     def alt_number (self,index):
  689.         '''Remove the candidates in Lookup Table from user_db index should start from 0'''
  690.         cps = self._lookup_table.get_current_page_start()
  691.         pos = cps + index
  692.         if  len (self._candidates[0]) > pos:
  693.             # this index is valid
  694.             can = self._candidates[0][pos]
  695.             if can[-2] < 0:
  696.                 # freq of this candidate is -1, means this a user phrase
  697.                 self.db.remove_phrase (can)
  698.                 # make update_candidates do sql enquiry
  699.                 self._chars[2].pop()
  700.                 self.update_candidates ()
  701.             return True
  702.         else:
  703.             return False
  704.  
  705.     def get_cursor_pos (self):
  706.         '''get lookup table cursor position'''
  707.         return self._lookup_table.get_cursor_pos()
  708.  
  709.     def get_lookup_table (self):
  710.         '''Get lookup table'''
  711.         return self._lookup_table
  712.  
  713.     def is_lt_visible (self):
  714.         '''Check whether lookup table is visible'''
  715.         return self._lookup_table.is_cursor_visible ()
  716.     
  717.     def backspace (self):
  718.         '''Process backspace Key Event'''
  719.         self._zi = u''
  720.         if self.get_input_chars():
  721.             self.pop_input ()
  722.             return True
  723.         elif self.get_preedit_strings ():
  724.             self.remove_before_char ()
  725.             return True
  726.         else:
  727.             return False
  728.     
  729.     def control_backspace (self):
  730.         '''Process control+backspace Key Event'''
  731.         self._zi = u''
  732.         if self.get_input_chars():
  733.             self.over_input ()
  734.             return True
  735.         elif self.get_preedit_strings ():
  736.             self.remove_before_string ()
  737.             return True
  738.         else:
  739.             return False
  740.  
  741.     def delete (self):
  742.         '''Process delete Key Event'''
  743.         self._zi = u''
  744.         if self.get_input_chars():
  745.             return True
  746.         elif self.get_preedit_strings ():
  747.             self.remove_after_char ()
  748.             return True
  749.         else:
  750.             return False
  751.     
  752.     def control_delete (self):
  753.         '''Process control+delete Key Event'''
  754.         self._zi = u''
  755.         if self.get_input_chars ():
  756.             return True
  757.         elif self.get_preedit_strings ():
  758.             self.remove_after_string ()
  759.             return True
  760.         else:
  761.             return False
  762.  
  763.     def l_shift (self):
  764.         '''Process Left Shift Key Event as immediately commit to preedit strings'''
  765.         if self._chars[0]:
  766.             self.commit_to_preedit ()
  767.             return True
  768.         else:
  769.             return False
  770.     
  771.     def r_shift (self):
  772.         '''Proess Right Shift Key Event as changed between PinYin Mode and Table Mode'''
  773.         self._zi = u''
  774.         if self._chars[0]:
  775.             self.commit_to_preedit ()
  776.         self._py_mode = not (self._py_mode)
  777.         return True
  778.  
  779.     def space (self):
  780.         '''Process space Key Event
  781.         return (KeyProcessResult,whethercommit,commitstring)'''
  782.         if self._chars[1]:
  783.             # we have invalid input, so do not commit 
  784.             return (False,u'')
  785.         if self._t_chars :
  786.             # user has input sth
  787.             istr = self.get_all_input_strings ()
  788.             self.commit_to_preedit ()
  789.             pstr = self.get_preedit_strings ()
  790.             #print "istr: ",istr
  791.             self.clear()
  792.             return (True,pstr,istr)
  793.         else:
  794.             return (False,u'',u'')
  795.     
  796.     def one_candidate (self):
  797.         '''Return true if there is only one candidate'''
  798.         return len(self._candidates[0]) == 1
  799.  
  800.  
  801. ########################
  802. ### Engine Class #####
  803. ####################
  804. class tabengine (ibus.EngineBase):
  805.     '''The IM Engine for Tables'''
  806.     
  807.     # colors
  808. #    _phrase_color             = 0xffffff
  809. #    _user_phrase_color         = 0xffffff
  810. #    _new_phrase_color         = 0xffffff
  811.  
  812.     # lookup table page size
  813.     _page_size = 6
  814.  
  815.     def __init__ (self, bus, obj_path, db ):
  816.         super(tabengine,self).__init__ (bus,obj_path)
  817.         self._lookup_table = ibus.LookupTable (tabengine._page_size)
  818.         # this is the backend sql db we need for our IME
  819.         # we receive this db from IMEngineFactory
  820.         #self.db = tabsqlitedb.tabsqlitedb( name = dbname )
  821.         self.db = db 
  822.         # this is the parer which parse the input string to key object
  823.         self._parser = tabdict.parse
  824.         
  825.         self._icon_dir = '%s%s%s%s' % (os.getenv('IBUS_TABLE_LOCATION'),
  826.                 os.path.sep, 'icons', os.path.sep)
  827.         # 0 = english input mode
  828.         # 1 = table input mode
  829.         self._mode = 1
  830.         # self._ime_py: True / False this IME support pinyin mode
  831.         self._ime_py = self.db.get_ime_property ('pinyin_mode')
  832.         if self._ime_py:
  833.             if self._ime_py.lower() == u'true':
  834.                 self._ime_py = True
  835.             else:
  836.                 self._ime_py = False
  837.         else:
  838.             print 'We coult not find "pinyin_mode" entry in database, is it a outdated database?'
  839.             self._ime_py = False
  840.  
  841.         self._status = self.db.get_ime_property('status_prompt').encode('utf8')
  842.         # now we check and update the valid input characters
  843.         self._chars = self.db.get_ime_property('valid_input_chars')
  844.         self._valid_input_chars = []
  845.         for _c in self._chars:
  846.             if _c in tabdict.tab_key_list:
  847.                 self._valid_input_chars.append(_c)
  848.         del self._chars
  849.         self._pt = self.db.get_phrase_table_index ()
  850.         self._ml = int(self.db.get_ime_property ('max_key_length'))
  851.         
  852.         # Containers we used:
  853.         self._editor = editor(self._pt, self._valid_input_chars, self._ml, self.db)
  854.  
  855.         # some other vals we used:
  856.         # self._prev_key: hold the key event last time.
  857.         self._prev_key = None
  858.         self._prev_char = None
  859.         self._double_quotation_state = False
  860.         self._single_quotation_state = False
  861.         # [ENmode,TABmode] we get TABmode properties from db
  862.         self._full_width_letter = [
  863.                 False, 
  864.                 self.db.get_ime_property('def_full_width_letter').lower() == u'true'
  865.                 ]
  866.         self._full_width_punct = [
  867.                 False,
  868.                 self.db.get_ime_property('def_full_width_punct').lower() == u'true'
  869.                 ]
  870.         # some properties we will involved, Property is taken from scim.
  871.         #self._setup_property = Property ("setup", _("Setup"))
  872.         try:
  873.             self._auto_commit = self.db.get_ime_property('auto_commit').lower() == u'true'
  874.         except:
  875.             self._auto_commit = False
  876.         # the commit phrases length
  877.         self._len_list = [0]
  878.         # connect to SpeedMeter
  879.         try:
  880.             bus = dbus.SessionBus()
  881.             user = os.path.basename( os.path.expanduser('~') )
  882.             self._sm_bus = bus.get_object ("org.ibus.table.SpeedMeter.%s"\
  883.                     % user, "/org/ibus/table/SpeedMeter")
  884.             self._sm =  dbus.Interface(self._sm_bus,\
  885.                     "org.ibus.table.SpeedMeter") 
  886.         except:
  887.             self._sm = None
  888.         self._sm_on = False
  889.         self._on = False
  890.         self.reset ()
  891.  
  892.     def reset (self):
  893.         self._editor.clear ()
  894.         self._double_quotation_state = False
  895.         self._single_quotation_state = False
  896.         self._prev_key = None
  897.         #self._editor._onechar = False    
  898.         self._init_properties ()
  899.         self._update_ui ()
  900.     
  901.     def do_destroy(self):
  902.         self.reset ()
  903.         self.focus_out ()
  904.         #self.db.sync_usrdb ()
  905.         super(tabengine,self).do_destroy()
  906.  
  907.     def _init_properties (self):
  908.         self.properties= ibus.PropList ()
  909.         self._status_property = ibus.Property(u'status')
  910.         if self.db._is_chinese:
  911.             self._cmode_property = ibus.Property(u'cmode')
  912.         self._letter_property = ibus.Property(u'letter')
  913.         self._punct_property = ibus.Property(u'punct')
  914.         self._py_property = ibus.Property(u'py_mode')
  915.         self._onechar_property = ibus.Property(u'onechar')
  916.         self._auto_commit_property = ibus.Property(u'acommit')
  917.         for prop in (self._status_property,
  918.             self._letter_property,
  919.             self._punct_property,
  920.             self._py_property,
  921.             self._onechar_property,
  922.             self._auto_commit_property
  923.             #self._setup_property
  924.             ):
  925.             self.properties.append(prop)
  926.         if self.db._is_chinese:
  927.             self.properties.insert( 1, self._cmode_property )
  928.         self.register_properties (self.properties)
  929.         self._refresh_properties ()
  930.     
  931.     def _refresh_properties (self):
  932.         '''Method used to update properties'''
  933.         # taken and modified from PinYin.py :)
  934.         if self._mode == 1: # refresh mode
  935.             if self._status == u'CN':
  936.                 self._status_property.set_icon( u'%s%s' % (self._icon_dir, 'chinese.svg') )
  937.                 self._status_property.set_label(  _(u'CN') )
  938.             else:
  939.                 self._status_property.set_icon( u'%s%s' % (self._icon_dir, 'ibus-table.svg') )
  940.                 self._status_property.set_label(  self._status )
  941.             self._status_property.set_tooltip (  _(u'Switch to English mode') )
  942.         else:
  943.             self._status_property.set_icon( u'%s%s' % (self._icon_dir, 'english.svg') )
  944.             self._status_property.set_label( _(u'EN') )
  945.             self._status_property.set_tooltip (  _(u'Switch to Table mode') )
  946.  
  947.         if self._full_width_letter[self._mode]:
  948.             self._letter_property.set_icon ( u'%s%s' % (self._icon_dir, 'full-letter.svg') )
  949.             self._letter_property.set_tooltip ( _(u'Switch to half letter') )
  950.         else:
  951.             self._letter_property.set_icon ( u'%s%s' % (self._icon_dir, 'half-letter.svg') )
  952.             self._letter_property.set_tooltip ( _(u'Switch to full letter') )
  953.  
  954.         if self._full_width_punct[self._mode]:
  955.             self._punct_property.set_icon ( u'%s%s' % (self._icon_dir, 'full-punct.svg') )
  956.             self._punct_property.set_tooltip ( _( u'Switch to half punction' ) )
  957.         else:
  958.             self._punct_property.set_icon ( u'%s%s' % (self._icon_dir,'half-punct.svg' ) )
  959.             self._punct_property.set_tooltip ( _( u'Switch to full punction' ) )
  960.         
  961.         if self._editor._py_mode:
  962.             self._py_property.set_icon ( u'%s%s' % (self._icon_dir, 'py-mode.svg' ) )
  963.             self._py_property.set_tooltip ( _(u'Switch to Table mode') )
  964.         
  965.         else:
  966.             self._py_property.set_icon ( u'%s%s' % (self._icon_dir, 'tab-mode.svg' ) )
  967.             self._py_property.set_tooltip ( _(u'Switch to PinYin mode') )
  968.  
  969.         if self._editor._onechar:
  970.             self._onechar_property.set_icon ( u'%s%s' % (self._icon_dir, 'onechar.svg' ))
  971.             self._onechar_property.set_tooltip ( _(u'Switch to phrase mode') )
  972.         else:
  973.             self._onechar_property.set_icon ( u'%s%s' % (self._icon_dir, 'phrase.svg' ))
  974.             self._onechar_property.set_tooltip ( _(u'Switch to single char mode') )
  975.         if self._auto_commit:
  976.             self._auto_commit_property.set_icon ( u'%s%s' % (self._icon_dir, 'acommit.svg' ) ) 
  977.             self._auto_commit_property.set_tooltip ( _(u'Switch to normal commit mode, which use space to commit') ) 
  978.         else:
  979.             self._auto_commit_property.set_icon ( u'%s%s' % (self._icon_dir, 'ncommit.svg' ) ) 
  980.             self._auto_commit_property.set_tooltip ( _(u'Switch to direct commit mode') ) 
  981.         # the chinese_mode:
  982.         if self.db._is_chinese:
  983.             if self._editor._chinese_mode == 0:
  984.                 self._cmode_property.set_icon ( u'%s%s' % (self._icon_dir,\
  985.                         'sc-mode.svg' ) ) 
  986.                 self._cmode_property.set_tooltip ( _(u'Switch to Traditional Chinese mode') ) 
  987.             elif self._editor._chinese_mode == 1:
  988.                 self._cmode_property.set_icon ( u'%s%s' % (self._icon_dir,\
  989.                         'tc-mode.svg' ) ) 
  990.                 self._cmode_property.set_tooltip ( _(u'Switch to Simplify Chinese first Big Charset Mode') ) 
  991.             elif self._editor._chinese_mode == 2:
  992.                 self._cmode_property.set_icon ( u'%s%s' % (self._icon_dir,\
  993.                         'scb-mode.svg' ) ) 
  994.                 self._cmode_property.set_tooltip ( _(u'Switch to Traditional Chinese first Big Charset Mode') ) 
  995.             elif self._editor._chinese_mode == 3:
  996.                 self._cmode_property.set_icon ( u'%s%s' % (self._icon_dir,\
  997.                         'tcb-mode.svg' ) ) 
  998.                 self._cmode_property.set_tooltip ( _(u'Switch to Big Charset Mode') ) 
  999.             elif self._editor._chinese_mode == 4:
  1000.                 self._cmode_property.set_icon ( u'%s%s' % (self._icon_dir,\
  1001.                         'cb-mode.svg' ) ) 
  1002.                 self._cmode_property.set_tooltip ( _(u'Switch to Simplify Chinese Mode') ) 
  1003.  
  1004.         # use buildin method to update properties :)
  1005.         map (self.update_property, self.properties)
  1006.     
  1007.     def _change_mode (self):
  1008.         '''Shift input mode, TAB -> EN -> TAB
  1009.         '''
  1010.         self._mode = int (not self._mode)
  1011.         self.reset ()
  1012.         self._update_ui ()
  1013.  
  1014.     def property_activate (self, property,prop_state = ibus.PROP_STATE_UNCHECKED):
  1015.         '''Shift property'''
  1016.         if property == u"status":
  1017.             self._change_mode ()
  1018.         elif property == u'py_mode' and self._ime_py:
  1019.             self._editor.r_shift ()
  1020.         elif property == u'onechar':
  1021.             self._editor._onechar = not self._editor._onechar
  1022.         elif property == u'acommit':
  1023.             self._auto_commit = not self._auto_commit
  1024.         elif property == u'letter':
  1025.             self._full_width_letter [self._mode] = not self._full_width_letter [self._mode]
  1026.         elif property == u'punct':
  1027.             self._full_width_punct [self._mode] = not self._full_width_punct [self._mode]
  1028.         elif property == u'cmode':
  1029.             self._editor.change_chinese_mode()
  1030.             self.reset()
  1031.         self._refresh_properties ()
  1032.     #    elif property == "setup":
  1033.             # Need implementation
  1034.     #        self.start_helper ("96c07b6f-0c3d-4403-ab57-908dd9b8d513")
  1035.         # at last invoke default method 
  1036.     
  1037.     def _update_preedit (self):
  1038.         '''Update Preedit String in UI'''
  1039.         _str = self._editor.get_preedit_strings ()
  1040.         if _str == u'':
  1041.             super(tabengine, self).update_preedit_text(ibus.Text(u'',None), 0, False)
  1042.         else:
  1043.             attrs = ibus.AttrList()
  1044.             res = patt_edit.match (_str)
  1045.             if res:
  1046.                 _str = u''
  1047.                 ures = patt_uncommit.match (res.group(1))
  1048.                 if ures:
  1049.                     _str=u''.join (ures.groups())
  1050.                     lc = len (ures.group(1) )
  1051.                     lu = len (ures.group(2) )
  1052.                     attrs.append (ibus.AttributeForeground(0x1b3f03,0,lc) )
  1053.                     attrs.append (ibus.AttributeForeground(0x0895a2,lc,lu) )
  1054.                     lg1 = len (_str)
  1055.                 else:
  1056.                     _str += res.group (1)
  1057.                     lg1 = len ( res.group(1) )
  1058.                     attrs.append (ibus.AttributeForeground(0x1b3f03,0,lg1) )
  1059.                 _str += res.group(2)
  1060.                 _str += res.group(3)
  1061.                 lg2 = len ( res.group(2) )
  1062.                 lg3 = len ( res.group(3) )
  1063.                 attrs.append( ibus.AttributeForeground(0x0e0ea0,lg1,lg2) )
  1064.                 attrs.append( ibus.AttributeForeground(0x1b3f03,lg1+lg2,lg3) )
  1065.             else:
  1066.                 attrs.append( ibus.AttributeForeground(0x1b3f03,0,len(_str)) )
  1067.             # because ibus now can only insert preedit into txt, so...
  1068.             attrs = ibus.AttrList()
  1069.             attrs.append(ibus.AttributeUnderline(ibus.ATTR_UNDERLINE_SINGLE, 0, len(_str)))
  1070.  
  1071.  
  1072.             super(tabengine, self).update_preedit_text(ibus.Text(_str, attrs), self._editor.get_caret(), True)
  1073.     
  1074.     def _update_aux (self):
  1075.         '''Update Aux String in UI'''
  1076.         _ic = self._editor.get_aux_strings ()
  1077.         if _ic:
  1078.             attrs = ibus.AttrList([ ibus.AttributeForeground(0x9515b5,0, len(_ic)) ])
  1079.             #attrs = [ scim.Attribute(0,len(_ic),scim.ATTR_FOREGROUND,0x5540c1)]
  1080.  
  1081.             super(tabengine, self).update_auxiliary_text(ibus.Text(_ic, attrs), True)
  1082.         else:
  1083.             self.hide_auxiliary_text()
  1084.             #self.update_aux_string (u'', None, False)
  1085.  
  1086.     def _update_lookup_table (self):
  1087.         '''Update Lookup Table in UI'''
  1088.         if self._editor.is_empty ():
  1089.             self.hide_lookup_table()
  1090.             return
  1091.         self.update_lookup_table ( self._editor.get_lookup_table(), True, True )    
  1092.  
  1093.     def _update_ui (self):
  1094.         '''Update User Interface'''
  1095.         self._update_lookup_table ()
  1096.         self._update_preedit ()
  1097.         self._update_aux ()
  1098.  
  1099.     def add_string_len(self, astring):
  1100.         if self._sm_on:
  1101.             try:
  1102.                 self._sm.Accumulate(len(astring))
  1103.             except:
  1104.                 pass
  1105.     
  1106.     def commit_string (self,string):
  1107.         self._editor.clear ()
  1108.         self._update_ui ()
  1109.         super(tabengine,self).commit_text ( ibus.Text(string) )
  1110.         self._prev_char = string[-1]
  1111.  
  1112.     def _convert_to_full_width (self, c):
  1113.         '''convert half width character to full width'''
  1114.         if c in [u".", u"\\", u"^", u"_", u"$", u"\"", u"'", u">", u"<" ]:
  1115.             if c == u".":
  1116.                 if self._prev_char and self._prev_char.isdigit () \
  1117.                     and self._prev_key and chr (self._prev_key.code) == self._prev_char:
  1118.                     return u"."
  1119.                 else:
  1120.                     return u"\u3002"
  1121.             elif c == u"\\":
  1122.                 return u"\u3001"
  1123.             elif c == u"^":
  1124.                 return u"\u2026\u2026"
  1125.             elif c == u"_":
  1126.                 return u"\u2014\u2014"
  1127.             elif c == u"$":
  1128.                 return u"\uffe5"
  1129.             elif c == u"\"":
  1130.                 self._double_quotation_state = not self._double_quotation_state
  1131.                 if self._double_quotation_state:
  1132.                     return u"\u201c"
  1133.                 else:
  1134.                     return u"\u201d"
  1135.             elif c == u"'":
  1136.                 self._single_quotation_state = not self._single_quotation_state
  1137.                 if self._single_quotation_state:
  1138.                     return u"\u2018"
  1139.                 else:
  1140.                     return u"\u2019"
  1141.             elif c == u"<":
  1142.                 if self._mode:
  1143.                     return u"\u300a"
  1144.             elif c == u">":
  1145.                 if self._mode:
  1146.                     return u"\u300b"
  1147.             
  1148.         return ibus.unichar_half_to_full (c)
  1149.     
  1150.     def _match_hotkey (self, key, code, mask):
  1151.         
  1152.         if key.code == code and key.mask == mask:
  1153.             if self._prev_key and key.code == self._prev_key.code and key.mask & modifier.RELEASE_MASK:
  1154.                 return True
  1155.             if not key.mask & modifier.RELEASE_MASK:
  1156.                 return True
  1157.  
  1158.         return False
  1159.     
  1160.     def process_key_event(self, keyval, keycode, state):
  1161.         '''Process Key Events
  1162.         Key Events include Key Press and Key Release,
  1163.         modifier means Key Pressed
  1164.         '''
  1165.         key = KeyEvent(keyval, state & modifier.RELEASE_MASK == 0, state)
  1166.         # ignore NumLock mask
  1167.         key.mask &= ~modifier.MOD2_MASK
  1168.  
  1169.         result = self._process_key_event (key)
  1170.         self._prev_key = key
  1171.         return result
  1172.  
  1173.     def _process_key_event (self, key):
  1174.         '''Internal method to process key event'''
  1175.         # Match mode switch hotkey
  1176.         if not self._editor._t_chars and ( self._match_hotkey (key, keysyms.Shift_L, modifier.SHIFT_MASK + modifier.RELEASE_MASK)):
  1177.             self._change_mode ()
  1178.             return True
  1179.  
  1180.         # Match full half letter mode switch hotkey
  1181.         if self._match_hotkey (key, keysyms.space, modifier.SHIFT_MASK):
  1182.             self.property_activate ("letter")
  1183.             return True
  1184.         
  1185.         # Match full half punct mode switch hotkey
  1186.         if self._match_hotkey (key, keysyms.period, modifier.CONTROL_MASK):
  1187.             self.property_activate ("punct")
  1188.             return True
  1189.         
  1190.         # we ignore all hotkeys
  1191. #        if key.mask & modifier.ALT_MASK:
  1192. #            return False
  1193.  
  1194.         # Ignore key release event
  1195. #        if key.mask & modifier.RELEASE_MASK:
  1196. #            return True
  1197.         
  1198.         if self._mode:
  1199.             return self._table_mode_process_key_event (key)
  1200.         else:
  1201.             return self._english_mode_process_key_event (key)
  1202.  
  1203.     def _english_mode_process_key_event (self, key):
  1204.         '''English Mode Process Key Event'''
  1205.         # Ignore key release event
  1206.         if key.mask & modifier.RELEASE_MASK:
  1207.             return True
  1208.         
  1209.         if key.code >= 128:
  1210.             return False
  1211.         # we ignore all hotkeys here    
  1212.         if key.mask & modifier.CONTROL_MASK+modifier.ALT_MASK:
  1213.             return False
  1214.         
  1215.         c = unichr (key.code)
  1216.         if ascii.ispunct (key.code): # if key code is a punctation
  1217.             if self._full_width_punct[self._mode]:
  1218.                 self.commit_string (self._convert_to_full_width (c))
  1219.                 return True
  1220.             else:
  1221.                 self.commit_string (c)
  1222.                 return True
  1223.             
  1224.         if self._full_width_letter[self._mode]: # if key code is a letter or digit
  1225.             self.commit_string (self._convert_to_full_width (c))
  1226.             return True
  1227.         else:
  1228.             self.commit_string (c)
  1229.             return True
  1230.         
  1231.         # should not reach there
  1232.         return False
  1233.     
  1234.     def _table_mode_process_key_event (self, key):
  1235.         '''Xingma Mode Process Key Event'''
  1236.         cond_letter_translate = lambda (c): \
  1237.             self._convert_to_full_width (c) if self._full_width_letter [self._mode] else c
  1238.         cond_punct_translate = lambda (c): \
  1239.             self._convert_to_full_width (c) if self._full_width_punct [self._mode] else c
  1240.         
  1241.         # We have to process the pinyin mode change key event here,
  1242.         # because we ignore all Release event below.
  1243.         if self._match_hotkey (key, keysyms.Shift_R, modifier.SHIFT_MASK + modifier.RELEASE_MASK) and self._ime_py:
  1244.             res = self._editor.r_shift ()
  1245.             self._refresh_properties ()
  1246.             self._update_ui ()
  1247.             return res
  1248.         # process commit to preedit    
  1249.         if self._match_hotkey (key, keysyms.Shift_R, modifier.SHIFT_MASK + modifier.RELEASE_MASK) or self._match_hotkey (key, keysyms.Shift_L, modifier.SHIFT_MASK + modifier.RELEASE_MASK):
  1250.             res = self._editor.l_shift ()
  1251.             self._update_ui ()
  1252.             return res
  1253.         
  1254.         # Match single char mode switch hotkey
  1255.         if self._match_hotkey (key, keysyms.comma, modifier.CONTROL_MASK):
  1256.             self.property_activate ( u"onechar" )
  1257.             return True
  1258.         # Match direct commit mode switch hotkey
  1259.         if self._match_hotkey (key, keysyms.slash, modifier.CONTROL_MASK):
  1260.             self.property_activate ( u"acommit" )
  1261.             return True
  1262.         
  1263.         # Match Chinese mode shift
  1264.         if self._match_hotkey (key, keysyms.semicolon, modifier.CONTROL_MASK):
  1265.             self.property_activate ( u"cmode" )
  1266.             return True
  1267.         
  1268.         # Match speedmeter shift
  1269.         if self._match_hotkey (key, keysyms.apostrophe, modifier.CONTROL_MASK):
  1270.             self._sm_on = not self._sm_on
  1271.             if self._sm_on:
  1272.                 self._sm.Show ()
  1273.             else:
  1274.                 self._sm.Hide ()
  1275.             return True
  1276.         
  1277.         # Ignore key release event now :)
  1278.         if key.mask & modifier.RELEASE_MASK:
  1279.             return True
  1280.  
  1281.         if self._editor.is_empty ():
  1282.             # we have not input anything
  1283.             if key.code <= 127 and ( unichr(key.code) not in self._valid_input_chars ) \
  1284.                     and (not key.mask & modifier.ALT_MASK + modifier.CONTROL_MASK):
  1285.                 if key.code == keysyms.space:
  1286.                     self.commit_string (cond_letter_translate (unichr (key.code)))
  1287.                     return True
  1288.                 if ascii.ispunct (key.code):
  1289.                     self.commit_string (cond_punct_translate (unichr (key.code)))
  1290.                     return True
  1291.                 if ascii.isdigit (key.code):
  1292.                     self.commit_string (cond_letter_translate (unichr (key.code)))
  1293.                     return True
  1294.             elif key.code > 127 and (not self._editor._py_mode):
  1295.                 return False
  1296.  
  1297.         if key.code == keysyms.Escape:
  1298.             self.reset ()
  1299.             self._update_ui ()
  1300.             return True
  1301.         
  1302.         elif key.code in (keysyms.Return, keysyms.KP_Enter):
  1303.             commit_string = self._editor.get_all_input_strings ()
  1304.             self.commit_string (commit_string)
  1305.             return True
  1306.         
  1307.         elif key.code in (keysyms.Down, keysyms.KP_Down) :
  1308.             res = self._editor.arrow_down ()
  1309.             self._update_ui ()
  1310.             return res
  1311.         
  1312.         elif key.code in (keysyms.Up, keysyms.KP_Up):
  1313.             res = self._editor.arrow_up ()
  1314.             self._update_ui ()
  1315.             return res
  1316.         
  1317.         elif key.code in (keysyms.Left, keysyms.KP_Left) and key.mask & modifier.CONTROL_MASK:
  1318.             res = self._editor.control_arrow_left ()
  1319.             self._update_ui ()
  1320.             return res
  1321.         
  1322.         elif key.code in (keysyms.Right, keysyms.KP_Right) and key.mask & modifier.CONTROL_MASK:
  1323.             res = self._editor.control_arrow_right ()
  1324.             self._update_ui ()
  1325.             return res
  1326.         
  1327.         elif key.code in (keysyms.Left, keysyms.KP_Left):
  1328.             res = self._editor.arrow_left ()
  1329.             self._update_ui ()
  1330.             return res
  1331.         
  1332.         elif key.code in (keysyms.Right, keysyms.KP_Right):
  1333.             res = self._editor.arrow_right ()
  1334.             self._update_ui ()
  1335.             return res
  1336.         
  1337.         elif key.code == keysyms.BackSpace and key.mask & modifier.CONTROL_MASK:
  1338.             res = self._editor.control_backspace ()
  1339.             self._update_ui ()
  1340.             return res
  1341.         
  1342.         elif key.code == keysyms.BackSpace:
  1343.             res = self._editor.backspace ()
  1344.             self._update_ui ()
  1345.             return res
  1346.         
  1347.         elif key.code == keysyms.Delete  and key.mask & modifier.CONTROL_MASK:
  1348.             res = self._editor.control_delete ()
  1349.             self._update_ui ()
  1350.             return res
  1351.         
  1352.         elif key.code == keysyms.Delete:
  1353.             res = self._editor.delete ()
  1354.             self._update_ui ()
  1355.             return res
  1356.  
  1357.         elif key.code >= keysyms._1 and key.code <= keysyms._9 and self._editor._candidates[0] and key.mask & modifier.CONTROL_MASK:
  1358.             res = self._editor.number (key.code - keysyms._1)
  1359.             self._update_ui ()
  1360.             return res
  1361.  
  1362.         elif key.code >= keysyms._1 and key.code <= keysyms._9 and self._editor._candidates[0] and key.mask & modifier.ALT_MASK:
  1363.             res = self._editor.alt_number (key.code - keysyms._1)
  1364.             self._update_ui ()
  1365.             return res
  1366.  
  1367.         elif key.code == keysyms.space:
  1368.             o_py = self._editor._py_mode
  1369.             sp_res = self._editor.space ()
  1370.             #return (KeyProcessResult,whethercommit,commitstring)
  1371.             if sp_res[0]:
  1372.                 self.commit_string (sp_res[1])
  1373.                 self.add_string_len(sp_res[1])
  1374.                 self.db.check_phrase (sp_res[1], sp_res[2])
  1375.             else:
  1376.                 if sp_res[1] == u' ':
  1377.                     self.commit_string (cond_letter_translate (u" "))
  1378.             if o_py != self._editor._py_mode:
  1379.                 self._refresh_properties ()
  1380.                 self._update_ui ()
  1381.             return True
  1382.         # now we ignore all else hotkeys
  1383.         elif key.mask & modifier.CONTROL_MASK+modifier.ALT_MASK:
  1384.             return False
  1385.  
  1386.         elif key.mask & modifier.ALT_MASK:
  1387.             return False
  1388.  
  1389.         elif unichr(key.code) in self._valid_input_chars or \
  1390.                 ( self._editor._py_mode and \
  1391.                     unichr(key.code) in u'abcdefghijklmnopqrstuvwxyz!@#$%' ):
  1392.             if self._auto_commit and ( len(self._editor._chars[0]) == self._ml \
  1393.                     or len (self._editor._chars[0]) in self.db.pkeylens ):
  1394.                 # it is time to direct commit
  1395.                 sp_res = self._editor.space ()
  1396.                 #return (whethercommit,commitstring)
  1397.                 if sp_res[0]:
  1398.                     self.commit_string (sp_res[1])
  1399.                     self.add_string_len(sp_res[1])
  1400.                     self.db.check_phrase (sp_res[1],sp_res[2])
  1401.             
  1402.             res = self._editor.add_input ( unichr(key.code) )
  1403.             if not res:
  1404.                 if ascii.ispunct (key.code):
  1405.                     key_char = cond_punct_translate (unichr (key.code))
  1406.                 else:
  1407.                     key_char = cond_letter_translate (unichr (key.code))
  1408.                 sp_res = self._editor.space ()
  1409.                 #return (KeyProcessResult,whethercommit,commitstring)
  1410.                 if sp_res[0]:
  1411.                     self.commit_string (sp_res[1] + key_char)
  1412.                     self.add_string_len(sp_res[1])
  1413.                     self.db.check_phrase (sp_res[1],sp_res[2])
  1414.                     return True
  1415.                 else:
  1416.                     self.commit_string ( key_char )
  1417.                     return True
  1418.             else:
  1419.                 if self._auto_commit and self._editor.one_candidate () and \
  1420.                         (len(self._editor._chars[0]) == self._ml \
  1421.                             or not self.db._is_chinese):
  1422.                     # it is time to direct commit
  1423.                     sp_res = self._editor.space ()
  1424.                     #return (whethercommit,commitstring)
  1425.                     if sp_res[0]:
  1426.                         self.commit_string (sp_res[1])
  1427.                         self.add_string_len(sp_res[1])
  1428.                         self.db.check_phrase (sp_res[1], sp_res[2])
  1429.                         return True
  1430.  
  1431.             self._update_ui ()
  1432.             return True
  1433.         
  1434.         elif key.code in (keysyms.equal, keysyms.Page_Down, keysyms.KP_Page_Down) and self._editor._candidates[0]:
  1435.             res = self._editor.page_down()
  1436.             self._update_lookup_table ()
  1437.             return res
  1438.  
  1439.         elif key.code in (keysyms.minus, keysyms.Page_Up, keysyms.KP_Page_Up) and self._editor._candidates[0]:
  1440.             res = self._editor.page_up ()
  1441.             self._update_lookup_table ()
  1442.             return res
  1443.         
  1444.         elif key.code >= keysyms._1 and key.code <= keysyms._9 and self._editor._candidates[0]:
  1445.             input_keys = self._editor.get_all_input_strings ()
  1446.             res = self._editor.number (key.code - keysyms._1)
  1447.             if res:
  1448.                 o_py = self._editor._py_mode
  1449.                 commit_string = self._editor.get_preedit_strings ()
  1450.                 self.commit_string (commit_string)
  1451.                 self.add_string_len(commit_string)
  1452.                 if o_py != self._editor._py_mode:
  1453.                     self._refresh_properties ()
  1454.                     self._update_ui ()
  1455.                 # modify freq info
  1456.                 self.db.check_phrase (commit_string, input_keys)
  1457.             return True
  1458.         
  1459.         elif key.code <= 127:
  1460.             if not self._editor._candidates[0]:
  1461.                 commit_string = self._editor.get_all_input_strings ()
  1462.             else:
  1463.                 self._editor.commit_to_preedit ()
  1464.                 commit_string = self._editor.get_preedit_strings ()
  1465.             self._editor.clear ()
  1466.             if ascii.ispunct (key.code):
  1467.                 self.commit_string ( commit_string + cond_punct_translate (unichr (key.code)))
  1468.             else:
  1469.                 self.commit_string ( commit_string + cond_letter_translate (unichr (key.code)))
  1470.             return True
  1471.         return False
  1472.     
  1473.     # below for initial test
  1474.     def focus_in (self):
  1475.         if self._on:
  1476.             self.register_properties (self.properties)
  1477.             self._refresh_properties ()
  1478.             self._update_ui ()
  1479.             try:
  1480.                 if self._sm_on:
  1481.                     self._sm.Show ()
  1482.                 else:
  1483.                     self._sm.Hide ()
  1484.             except:
  1485.                 pass
  1486.     
  1487.     def focus_out (self):
  1488.         try:
  1489.             self._sm.Hide()
  1490.         except:
  1491.             pass
  1492.  
  1493.     def enable (self):
  1494.         try:
  1495.             self._sm.Reset()
  1496.         except:
  1497.             pass
  1498.         self._on = True
  1499.         self.focus_in()
  1500.  
  1501.     def disable (self):
  1502.         self.reset()
  1503.         try:
  1504.             self._sm.Hide()
  1505.         except:
  1506.             pass
  1507.         self._on = False
  1508.  
  1509.  
  1510.     def lookup_table_page_up (self):
  1511.         if self._editor.page_up ():
  1512.             self._update_lookup_table ()
  1513.             return True
  1514.         return True
  1515.  
  1516.     def lookup_table_page_down (self):
  1517.         if self._editor.page_down ():
  1518.             self._update_lookup_table ()
  1519.             return True
  1520.         return False
  1521.     
  1522.     
  1523.